package com.tomtop.core.datasource.core;

import java.sql.Connection;
import java.sql.SQLException;
import java.util.Iterator;
import java.util.LinkedHashMap;
import java.util.LinkedList;
import java.util.List;
import java.util.Map;
import java.util.Stack;

import javax.sql.DataSource;

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.jdbc.datasource.AbstractDataSource;
import org.springframework.scheduling.annotation.Async;

import com.tomtop.core.datasource.config.DruidNode;
import com.tomtop.core.datasource.config.DruidProperties;

/**
 * 配置主从数据源后，根据选择，返回对应的数据源。多个从库的情况下，会平均的分配从库，用于负载均衡。
 *
 * @author tanghd
 */

public class DynamicDataSource extends AbstractDataSource  {

    private static final Logger logger = LoggerFactory.getLogger(DynamicDataSource.class);
	
    
	private Map<String, DataSource> resolvedDataSources=new LinkedHashMap<String,DataSource>();
	private DataSource masterDataSource;
	private DruidNode druidNode;
    private List<String> slavesDataSourceNames=new LinkedList<>();
    private List<String> slavesFailureDataSourceNames=new LinkedList<>();
    private static final ThreadLocal<Stack<Boolean>> ismaster=new ThreadLocal<Stack<Boolean>>(){protected Stack<Boolean> initialValue(){ return new Stack<Boolean>();}};
    
    private static int selectnum=0;
    
    protected static void useMaster() { 
    	ismaster.get().push(true); 
    }

    protected static void useSlave() { 
    	ismaster.get().push(false); 
    }

    protected static void reset() { 
    	ismaster.get().pop();
    	if(ismaster.get().size()==0)
    		ismaster.remove();
    }
 

	@Override
	public Connection getConnection() throws SQLException {
		try {
			return determineTargetDataSourceConnection();
		} catch (Throwable e) {
			return determineTargetDataSourceConnection();
		}
	}

	@Override
	public Connection getConnection(String username, String password) throws SQLException {
		try {
			return determineTargetDataSourceConnection(username, password);
		} catch (Throwable e) {
			return determineTargetDataSourceConnection(username, password);
		}
	}

	private Connection determineTargetDataSourceConnection() throws SQLException {
		String lookupKey = determineCurrentLookupKey();
		DataSource datasource = determineTargetDataSource(lookupKey);
		try {
			return datasource.getConnection();
		} catch (SQLException e) {
			this.recordFailure(datasource, lookupKey,e);
			throw e;
		}
	}
	
	private Connection determineTargetDataSourceConnection(String username, String password) throws SQLException {
		String lookupKey = determineCurrentLookupKey();
		DataSource datasource = determineTargetDataSource(lookupKey);
		try {
			return datasource.getConnection(username, password);
		} catch (SQLException e){
			this.recordFailure(datasource, lookupKey,e);
			throw e;
		}
	}
	
	private void recordFailure(DataSource datasource,String lookupKey,SQLException e) {
		if(this.masterDataSource.equals(datasource)){
			logger.error("数据库主库出现异常，请检查主库状态。。。异常信息:{} {}  {}",e.getMessage(),e.getSQLState(),e.getErrorCode());
			return;
		}
		slavesDataSourceNames.remove(lookupKey);
		slavesFailureDataSourceNames.add(lookupKey);
		logger.warn("数据库从库{}出现异常，已下线。。。异常信息:{} {}  {}",lookupKey,e.getMessage(),e.getSQLState(),e.getErrorCode());
	}
	
	@Async
	public void testFailureSlavesDataSource(){
		if(slavesFailureDataSourceNames.isEmpty())
			return;
		Iterator<String> it = slavesFailureDataSourceNames.iterator();
		while (it.hasNext()) {
			String lookupKey = (String) it.next();
			try {
				determineTargetDataSource(lookupKey).getConnection();
				slavesFailureDataSourceNames.remove(lookupKey);
				slavesDataSourceNames.add(lookupKey);
				logger.info("数据库从库{}从异常中恢复过来",lookupKey );
			} catch (Exception e) { logger.debug("测试链接失效的从库{}还没有活过来",lookupKey,e);}
		}
	}
	
	@Override
	@SuppressWarnings("unchecked")
	public <T> T unwrap(Class<T> iface) throws SQLException {
		if (iface.isInstance(this)) {
			return (T) this;
		}
		String lookupKey = determineCurrentLookupKey();
		return determineTargetDataSource(lookupKey).unwrap(iface);
	}

	@Override
	public boolean isWrapperFor(Class<?> iface) throws SQLException {
		String lookupKey = determineCurrentLookupKey();
		return (iface.isInstance(this) || determineTargetDataSource(lookupKey).isWrapperFor(iface));
	}

	protected DataSource determineTargetDataSource(String lookupKey) {
		DataSource dataSource = this.resolvedDataSources.get(lookupKey);
		if (dataSource != null)
			return dataSource;
		return this.masterDataSource;
	}

	
    /**
     * 如果是选择使用从库，且从库的数量大于1，则通过取模来控制从库的负载,
     */
    protected String determineCurrentLookupKey() {
    	if(ismaster.get().isEmpty()||ismaster.get().peek()){
    		return null;
    	}
    	if(slavesDataSourceNames!=null){
    		String slavesDataSourceName = slavesDataSourceNames.get(selectnum=(++selectnum %(slavesDataSourceNames.size())));
    		logger.info("切换到从库{}中查询",slavesDataSourceName);
    		return slavesDataSourceName;
    	}
    	return null;
    }
   
    public DruidNode getDruidNode() {
		return druidNode;
	}
    public static DynamicDataSource create(DruidNode druidNode,DruidProperties defaultDruidProperties,String dataSourceName) throws SQLException{
    	return new DynamicDataSource(druidNode, defaultDruidProperties ,dataSourceName);
    }
    
    public DynamicDataSource() {
	}
    
    public DynamicDataSource(DruidNode druidNode,DruidProperties defaultDruidProperties,String dataSourceName) throws SQLException {
      	this.druidNode=druidNode;
    	DruidProperties master = druidNode.getMaster();
    	if(master==null)master=new DruidProperties();
    	master.merge(defaultDruidProperties).defaultEmpty().setDefaultReadOnly(false);;
    	this.masterDataSource=master.createDataSource();
    	List<DruidProperties> slaves = druidNode.getSlaves();
    	if(slaves!=null&&!slaves.isEmpty()){
    		for (int i = 0; i < slaves.size(); i++) {
				DruidProperties slave = slaves.get(i);
				if(slave==null)continue;
				slave.merge(defaultDruidProperties).defaultEmpty().setDefaultReadOnly(true);;
				String datasourcename=dataSourceName+"-Slave-"+i;
				this.slavesDataSourceNames.add(datasourcename);
				this.resolvedDataSources.put(datasourcename, slave.createDataSource());
			}
    	}
	}
}
